Skip to content

Add assertThrows variants which accept exceptions as values#5819

Open
mdedetrich wants to merge 1 commit into
junit-team:mainfrom
mdedetrich:assert-throws-exceptions-as-value
Open

Add assertThrows variants which accept exceptions as values#5819
mdedetrich wants to merge 1 commit into
junit-team:mainfrom
mdedetrich:assert-throws-exceptions-as-value

Conversation

@mdedetrich

@mdedetrich mdedetrich commented Jun 30, 2026

Copy link
Copy Markdown

Currently there exists org.junit.jupiter.api.assertThrows and its various variants to be used with JUnit and Kotlin that are also compatible with coroutines (i.e. suspend func). The current design uses reified inline functions, which generally work aside from one situation, specifically

  1. You are using @ParameterizedTest that produces exceptions classes as values and you intend to use assertThrows on those exception values.
  2. The function/method you are testing is a kotlin coroutine (suspend func)

In this case, the current org.junit.jupiter.api.assertThrows doesn't work because in order to use it you would have to make your test function (the one annotated with @ParameterizedTest) an inline func with a reified T: Throwable however this won't be picked up by JUnit as @ParameterizedTest uses reflection.

This PR adds variants of the currently existing org.junit.jupiter.api.assertThrows that have an expectedType: Class<T> as the first parameter (which is the same convention as org.junit.jupiter.api.Assertions.assertThrows) but since these added variants are inline fun, functions/methods used in the body can be suspend func (this is verified with the added tests).

The current implementation is a bit of boilerplate however this is inevitable to a degree since we cannot use the currently existing org.junit.jupiter.api.assertThrows and its variants implementations as they only accept reified T: Throwable which then defeats the purpose of what we are trying to achieve.


I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

@marcphilipp

Copy link
Copy Markdown
Member

@mdedetrich Can't you use Assertions.assertThrows directly?

@mdedetrich

mdedetrich commented Jul 1, 2026

Copy link
Copy Markdown
Author

@mdedetrich Can't you use Assertions.assertThrows directly?

Not with a kotlin suspend function that you are asserting, thats the precise issue. It will fail to compile for the reasons I stated in the PR. Kotlin suspend only works if the assertThrow definitions are inline func (which Assertions.assertThrows is not since its written in Java) or if the block parameter argument has suspend func () -> definition.

Of course you could just do

Assertions.assertThrows(IllegalArgumentException::class.java) {
  runBlocking {
    someKotlinSuspendFunc()
  }
}

But one of the whole point of org.junit.jupiter.api.* assert functions existing is so that they can work with kotlin coroutines directly without the using of runBlocking/runTest.

I have just rebased the PR against latest main to resolve conflicts.

Comment thread junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt Outdated
@marcphilipp

Copy link
Copy Markdown
Member

Kotlin suspend only works if the assertThrow definitions are inline func

Sorry for making you re-explain it. I didn't read carefully enough. 😬

Signed-off-by: Matthew de Detrich <matthew.dedetrich@gls-itservices.com>
@mdedetrich mdedetrich force-pushed the assert-throws-exceptions-as-value branch from 95ba98b to d3494e9 Compare July 1, 2026 07:58
@mdedetrich mdedetrich requested a review from marcphilipp July 1, 2026 11:12
@marcphilipp

Copy link
Copy Markdown
Member

I wonder whether having the reified and the new variants is confusing and we should deprecate the old ones. WDYT? 🤔

@mdedetrich

mdedetrich commented Jul 2, 2026

Copy link
Copy Markdown
Author

I wonder whether having the reified and the new variants is confusing and we should deprecate the old ones. WDYT? 🤔

I personally see value in both and actually prefer the original variants. Its just that there are some cases where you are using exceptions as values (i.e. @ParameterizedTest) and there isn't anything you can do about it.

While its true that there are a lot of overloads, the design is intelligent in that the ones that use exception values always have exception as first arg so the quality of life of users with IDE's should be a non issue (talking about ergonomics of finding the right overload).

tld;r I think its a non issue and there is value in both cases, like generally with JVM languages in some cases you treat types as values and in other cases as type parameters.

@testlens-app

testlens-app Bot commented Jul 2, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: d3494e9
▶️ Tests: 38566 executed
⚪️ Checks: 17/17 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants